/// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Microsoft.VSPowerToys.Common.TaskManager
{
    /// <summary>
    /// A task scheduler that currently launches task in series. All tasks that can be used
    /// with this task manager must implement base Task class.
    /// </summary>
    public class TaskScheduler
    {

        #region Private Members

        private Queue<Task> queuedTasks = new Queue<Task>();

        private bool isRunning = false;

        private Thread workerThread = null;

        private bool queueModified = true;
        
        /// <summary>
        /// Currently running task
        /// </summary>
        private Task currentTask;

        /// <summary>
        /// Locking object to be used to access isRunning variables
        /// </summary>
        private object runningSyncObject = new object();

        #endregion

        #region Event Declarations

        /// <summary>
        /// This event is raised when a task is starting. It allows
        /// task to be modified before it begins
        /// </summary>
		public event EventHandler<TaskEventArgs> TaskStarting;

        /// <summary>
        /// This event is raised when all items in the queue are completed.
        /// When this event is raised, task manager still continues to watch
        /// for new tasks. Task manager must be stopped by user.
        /// </summary>
        public event EventHandler QueueCompleted;

        /// <summary>
        /// This event is raised when a task is completed either successfully or
        /// unsuccessfully.
        /// </summary>
        public event EventHandler<TaskCompletedEventArgs> TaskCompleted;

        /// <summary>
        /// This event is raised when a task is aborted.
        /// </summary>
        public event EventHandler<TaskAbortedEventArgs> TaskAborted;

        #endregion

        #region Public Methods

        /// <summary>
        /// Enqueues a new task in to task manager.
        /// </summary>
        /// <param name="task"></param>
        public void EnqueueTask(Task task)
        {
            Monitor.Enter(this.queuedTasks);
            try
            {
                this.queuedTasks.Enqueue(task);
                this.queueModified = true;
                Monitor.Pulse(this.queuedTasks);
            }
            finally
            {
                Monitor.Exit(this.queuedTasks);
            }
        }

        /// <summary>
        /// Starts the task monitor.
        /// </summary>
        public void Start()
        {
            lock (this.runningSyncObject)
            {
                this.isRunning = true;
            }
            if (this.workerThread == null || !this.workerThread.IsAlive)
            {
                this.workerThread = new Thread(new ThreadStart(this.ProcessQueue));
                this.workerThread.Start();
            }
        }

        /// <summary>
        /// Stops queue processing. This operation does not stop currently
        /// executing tasks
        /// </summary>
        public void Stop(bool wait)
        {
            lock (this.runningSyncObject)
            {
                this.isRunning = false;
                if (this.currentTask != null && this.currentTask.IsCancellable)
                {
                    this.currentTask.CancelTask();
                }
            }
            if (wait && this.workerThread != null)
            {
                this.workerThread.Join();
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// This method runs on a seperate thread and contionusly monitors task queue
        /// for new tasks and run those tasks in order.
        /// 
        /// Currently it doesn't support parallel task execution.
        /// </summary>
        private void ProcessQueue()
        {
            bool stopRunning;
            lock (this.runningSyncObject)
            {
                stopRunning = !this.isRunning;
            }
            while (!stopRunning)
            {
                Monitor.Enter(this.queuedTasks);
                try
                {
                    lock (this.runningSyncObject)
                    {
                        this.currentTask = null;
                    }
                    while (this.queuedTasks.Count == 0)
                    {
                        if (this.queueModified)
                        {
                            if (this.QueueCompleted != null)
                            {
                                this.QueueCompleted(this, null);
                            }
                            this.queueModified = false;
                        }
                        Monitor.Wait(this.queuedTasks, 1000);
                        lock (this.runningSyncObject)
                        {
                            if (!this.isRunning) return;
                        }
                    }
                    lock (this.runningSyncObject)
                    {
                        currentTask = this.queuedTasks.Dequeue();
                    }
                }
                finally
                {
                    Monitor.Exit(this.queuedTasks);
                }
                if (this.TaskStarting != null)
                {
                    this.TaskStarting(this, new TaskEventArgs(currentTask));
                }
                try
                {
                    currentTask.PerformTask();
                    if (this.TaskCompleted != null)
                    {
                        this.TaskCompleted(this, new TaskCompletedEventArgs(currentTask));
                    }
                }
                catch (TaskException ex)
                {
                    if (this.TaskAborted != null)
                    {
                        this.TaskAborted(this, new TaskAbortedEventArgs(ex));
                    }
                    if (this.TaskCompleted != null)
                    {
                        this.TaskCompleted(this, new TaskCompletedEventArgs(currentTask, false, ex));
                    }
                }
                lock (this.runningSyncObject)
                {
                    stopRunning = !this.isRunning;
                }
            }
        }
        #endregion
    }

    /// <summary>
    /// A base class for tasks accepted by task manager class.  
    /// </summary>
    public abstract class Task
    {

        #region Private Members 

        private string taskId;

        private object tag;
        
        #endregion

        #region Public Properties

        /// <summary>
        /// Task identifier, this can be used to identify the task. Provided by user.
        /// </summary>
        public string TaskId
        {
            get
            {
                return taskId;
            }
        }

        /// <summary>
        /// A custom user tag assigned to this task
        /// </summary>
        public object Tag
        {
            get
            {
                return tag;
            }

            set
            {
                tag = value;
            }
        }

        /// <summary>
        /// Returns true if task is cancellable during its execution
        /// </summary>
        public abstract bool IsCancellable
        {
            get;
        }

        #endregion

        #region Constructors

        protected Task(string id)
        {
            this.taskId = id;
        }
        #endregion

        #region Event Declerations

        /// <summary>
        /// This event is raised when a new progress report is available for
        /// the executing task.
        /// </summary>
		public event EventHandler<TaskProgressEventArgs> ProgressAvailable;

        /// <summary>
        /// Raised when a task posts a message regarding the task process.
        /// </summary>
        public event EventHandler<TaskMessageAvailableEventArgs> TaskMessageAvailable;
        #endregion

        #region Public Methods

        /// <summary>
        /// Implementing classes should override this method to perform their task.
        /// Once the task completes, method should return normally. All failures
        /// should be reported by raising a TaskException exception.
        /// </summary>
        public abstract void PerformTask();

        /// <summary>
        /// Cancels the task if it is cancellabe
        /// </summary>
        public void CancelTask()
        {
            if (this.IsCancellable)
            {
                this.CancelTaskInternal();
            }
            else
            {
                throw new InvalidOperationException();
            }
        }
        #endregion

        #region Protected Methods

        /// <summary>
        /// This method should be called by implementing classes whenever they want to report their progress.
        /// </summary>
        /// <param name="current"></param>
        /// <param name="max"></param>
        protected void ReportProgress(int current, int max)
        {
            if (this.ProgressAvailable != null)
            {
                this.ProgressAvailable(this, new TaskProgressEventArgs(this, current, max));
            }
        }

        /// <summary>
        /// This method should be called by implementing classes whenever they want to post a message to user
        /// regarding installation process
        /// </summary>
        /// <param name="message">Message to post</param>
        protected void PostMessage(string message)
        {
            if (this.TaskMessageAvailable != null)
            {
                this.TaskMessageAvailable(this, new TaskMessageAvailableEventArgs(this, message));
            }
        }
        /// <summary>
        /// Cancels the task, by default this method does not do anything and should be overridden
        /// by tasks that are cancellable.
        /// </summary>
        protected virtual void CancelTaskInternal()
        {
        }
        #endregion
    }



}
